﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Net.Sockets;

namespace NickPowell.LiveCode
{
    public class User
    {
        private IOHandler ioHandler;
        private Project project;

        private LiveCode form;

        private string sUsername = "";
        private int iUserID = -1;
        /// <summary>
        /// Dictionary of all projects the user is in
        /// </summary>
        private Dictionary<int, string> dicUserProjects = new Dictionary<int, string>();

        private byte[] bytDataBuffer = new byte[2048];

        private List<byte[]> lstQuickRecieveDataBuffers = new List<byte[]>();
        private List<byte[]> lstSlowRecieveDataBuffers = new List<byte[]>();

        public IOHandler IOHandler
        {
            get
            {
                return this.ioHandler;
            }
        }
        public int ID
        {
            get
            {
                return this.iUserID;
            }
        }

        public User(LiveCode _form)
        {
            this.form = _form;
            this.ioHandler = new IOHandler(new AsyncCallback(OnClientConnect));
        }

        public Dictionary<int, string> UserProjects
        {
            get
            {
                return this.dicUserProjects;
            }
        }

        #region Login
        public bool Login(string _sUsername, string _sPassword)
        {
            try
            {
                this.sUsername = _sUsername;

                XmlDocument xmlLogin = new XmlDocument();
                XmlNode xmlLiveCode = xmlLogin.AppendChild(xmlLogin.CreateElement("LiveCode"));
                XmlNode xmlLoginNode = xmlLiveCode.AppendChild(xmlLogin.CreateElement("Login"));
                XmlNode xmlUsername = xmlLoginNode.AppendChild(xmlLogin.CreateElement("Username"));
                xmlUsername.InnerText = this.sUsername;
                XmlNode xmlPassword = xmlLoginNode.AppendChild(xmlLogin.CreateElement("Password"));
                xmlPassword.InnerText = _sPassword;

                XmlDocument xmlServerResponse = this.ioHandler.SyncSendRecieve(xmlLogin);
                this.SlowProcessRecievedXML(xmlServerResponse);

                if (this.iUserID != -1)
                {
                    return true;
                }

                return false;
            }
            catch (SocketException socExp)
            {
                //Connection was refused (unable to connect to server)
                if (socExp.ErrorCode == 10061)
                {
                    System.Windows.Forms.MessageBox.Show("Unable to connect to server");
                    return false;
                }
                else
                {
                    throw socExp;
                }
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        public void ProjectLogin(int iProjectID)
        {
            try
            {
                XmlDocument xmlProjectLogin = new XmlDocument();
                XmlNode xmlLiveCode = xmlProjectLogin.AppendChild(xmlProjectLogin.CreateElement("LiveCode"));
                XmlNode xmlProjLogin = xmlLiveCode.AppendChild(xmlProjectLogin.CreateElement("ProjectLogin"));
                XmlNode xmlUserID = xmlProjLogin.AppendChild(xmlProjectLogin.CreateElement("UserID"));
                xmlUserID.InnerText = this.iUserID.ToString();
                XmlNode xmlProjectID = xmlProjLogin.AppendChild(xmlProjectLogin.CreateElement("ProjectID"));
                xmlProjectID.InnerText = iProjectID.ToString();

                this.ioHandler.SlowSend(xmlProjectLogin);
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        public void Logoff()
        {
            try
            {
                this.ioHandler.DisconnectFromServer();

                this.form.Logoff();
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        #endregion

        private void WaitForQuickData(SocketInfo socketInfo)
        {
            try
            {
                socketInfo.ConnectionSocket.BeginReceive(this.bytDataBuffer, 0, this.bytDataBuffer.Length, SocketFlags.None, new AsyncCallback(OnQuickDataRecieved), socketInfo);
            }
            catch (SocketException socEx)
            {
                throw socEx;
            }
        }

        private void WaitForSlowData(SocketInfo socketInfo)
        {
            try
            {
                socketInfo.ConnectionSocket.BeginReceive(this.bytDataBuffer, 0, this.bytDataBuffer.Length, SocketFlags.None, new AsyncCallback(OnSlowDataRecieved), socketInfo);
            }
            catch (SocketException socEx)
            {
                throw socEx;
            }
        }

        #region Process Data
        private void SlowProcessRecievedXML(XmlDocument xmlRecieved)
        {
            if (xmlRecieved.SelectSingleNode("/LiveCode/Login") != null)
            {
                this.ProcessLogin(xmlRecieved);
            }
            else if (xmlRecieved.SelectSingleNode("/LiveCode/Logout") != null)
            {
                this.Logoff();
            }
            else if (xmlRecieved.SelectSingleNode("/LiveCode/Project") != null)
            {
                this.ProcessProject(xmlRecieved);
            }
            else if (xmlRecieved.SelectSingleNode("/LiveCode/Chat") != null)
            {
                this.ProcessChat(xmlRecieved);
            }
        }

        private void QuickProcessRecievedXML(XmlDocument xmlRecieved)
        {
            //If a text changed event
            if (xmlRecieved.SelectSingleNode("/LiveCode/Text") != null)
            {
                this.project.ProcessTextChange(xmlRecieved);
            }
        }

        private void ProcessLogin(XmlDocument xmlLogin)
        {
            //Make sure this document has user login info
            if (xmlLogin.SelectSingleNode("/LiveCode/Login/User") != null)
            {
                //Get the user id
                this.iUserID = int.Parse(xmlLogin.SelectSingleNode("/LiveCode/Login/User/UserID").InnerText);

                XmlNodeList xmlProjects = xmlLogin.SelectNodes("/LiveCode/Login/User/Projects/Project");
                foreach (XmlNode project in xmlProjects)
                {
                    int iProjectID = int.Parse(project.SelectSingleNode("ID").InnerText);
                    string sProjectName = project.SelectSingleNode("Name").InnerText;

                    this.dicUserProjects.Add(iProjectID, sProjectName);
                }
            }

            this.ProcessPorts(xmlLogin);
        }

        private void ProcessPorts(XmlDocument xmlPorts)
        {
            if (xmlPorts.SelectSingleNode("/LiveCode/Ports") != null)
            {
                XmlNodeList xmlSlowSendPorts = xmlPorts.SelectNodes("/LiveCode/Ports/SlowSend/PortNum");
                XmlNodeList xmlSlowRecievePorts = xmlPorts.SelectNodes("/LiveCode/Ports/SlowRecieve/PortNum");
                XmlNodeList xmlQuickSendPorts = xmlPorts.SelectNodes("/LiveCode/Ports/QuickSend/PortNum");
                XmlNodeList xmlQuickRecievePorts = xmlPorts.SelectNodes("/LiveCode/Ports/QuickRecieve/PortNum");

                List<int> lstSlowSendPorts = new List<int>();
                foreach (XmlNode slowSendPort in xmlSlowSendPorts)
                {
                    string sPort = slowSendPort.InnerText;
                    int iPort = -1;
                    if (int.TryParse(sPort, out iPort))
                    {
                        lstSlowSendPorts.Add(iPort);
                    }
                }

                List<int> lstSlowRecievePorts = new List<int>();
                foreach (XmlNode slowRecievePort in xmlSlowRecievePorts)
                {
                    string sPort = slowRecievePort.InnerText;
                    int iPort = -1;
                    if (int.TryParse(sPort, out iPort))
                    {
                        lstSlowRecievePorts.Add(iPort);
                    }
                }

                List<int> lstQuickSendPorts = new List<int>();
                foreach (XmlNode quickSendPort in xmlQuickSendPorts)
                {
                    string sPort = quickSendPort.InnerText;
                    int iPort = -1;
                    if (int.TryParse(sPort, out iPort))
                    {
                        lstQuickSendPorts.Add(iPort);
                    }
                }

                List<int> lstQuickRecievePorts = new List<int>();
                foreach (XmlNode quickRecievePort in xmlQuickRecievePorts)
                {
                    string sPort = quickRecievePort.InnerText;
                    int iPort = -1;
                    if (int.TryParse(sPort, out iPort))
                    {
                        lstQuickRecievePorts.Add(iPort);
                    }
                }

                for (int i = 0; i < lstQuickRecievePorts.Count; i++)
                {
                    this.lstQuickRecieveDataBuffers.Add(new byte[256]);
                }

                for (int i = 0; i < lstSlowRecievePorts.Count; i++)
                {
                    this.lstSlowRecieveDataBuffers.Add(new byte[4096]);
                }

                this.ioHandler.SetSockets(lstSlowSendPorts, lstSlowRecievePorts, lstQuickSendPorts, lstQuickRecievePorts);
            }
        }

        private void ProcessProject(XmlDocument xmlProject)
        {
            try
            {
                //Make sure this is a single project node
                if (xmlProject.SelectNodes("/LiveCode/Project").Count == 1)
                {
                    int iProjectID = int.Parse(xmlProject.SelectSingleNode("/LiveCode/Project/@id").Value);
                    string sProjectName = xmlProject.SelectSingleNode("/LiveCode/Project/@name").Value;

                    string sFilePath = xmlProject.SelectSingleNode("/LiveCode/Project/ClientLocation").InnerText;
                    List<string> lstRelativeFilePaths = new List<string>();
                    XmlNodeList xmlRelativePaths = xmlProject.SelectNodes("/LiveCode/Project/RelativePaths/FilePath");
                    foreach (XmlNode xmlPath in xmlRelativePaths)
                    {
                        lstRelativeFilePaths.Add(xmlPath.InnerText);
                    }

                    this.project = new Project(iProjectID, sProjectName, sFilePath, lstRelativeFilePaths, this);
                }
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        private void ProcessChat(XmlDocument xmlChat)
        {
            //Make sure this is the correct document type
            if (xmlChat.SelectSingleNode("/LiveCode/Chat") != null)
            {
                //See which action to take
                if (xmlChat.SelectSingleNode("/LiveCode/Chat/UserLogoff") != null)
                {
                    int iUserID = int.Parse(xmlChat.SelectSingleNode("/LiveCode/Chat/UserLogoff/@id").Value);

                    this.form.ChatUserLogoff(iUserID);
                }
                else if (xmlChat.SelectSingleNode("/LiveCode/Chat/UserLogon") != null)
                {
                    int iUserID = int.Parse(xmlChat.SelectSingleNode("/LiveCode/Chat/UserLogon/@id").Value);
                    string sName = xmlChat.SelectSingleNode("/LiveCode/Chat/UserLogon/@name").Value;

                    this.form.ChatUserLogon(iUserID, sName);
                }
                else if (xmlChat.SelectSingleNode("/LiveCode/Chat/Message") != null)
                {
                    int iToID = int.Parse(xmlChat.SelectSingleNode("/LiveCode/Chat/Message/@toID").Value);
                    int iFromID = int.Parse(xmlChat.SelectSingleNode("/LiveCode/Chat/Message/@fromID").Value);
                    string sText = xmlChat.SelectSingleNode("/LiveCode/Chat/Message/@text").Value;

                    //Make sure this message is to the correct user
                    if (iToID == this.ID)
                    {
                        this.form.ChatMessage(iFromID, sText);
                    }
                }
            }
        }
        #endregion

        public void SendChatMessage(int iToID, string sText)
        {
            XmlDocument xmlChatMessage = new XmlDocument();
            XmlNode xmlLiveCode = xmlChatMessage.AppendChild(xmlChatMessage.CreateElement("LiveCode"));
            XmlNode xmlChat = xmlLiveCode.AppendChild(xmlChatMessage.CreateElement("Chat"));
            XmlNode xmlMessage = xmlChat.AppendChild(xmlChatMessage.CreateElement("Message"));
            XmlAttribute xmlToID = xmlMessage.Attributes.Append(xmlChatMessage.CreateAttribute("toID"));
            xmlToID.Value = iToID.ToString();
            XmlAttribute xmlFromID = xmlMessage.Attributes.Append(xmlChatMessage.CreateAttribute("fromID"));
            xmlFromID.Value = this.iUserID.ToString();
            XmlAttribute xmlText = xmlMessage.Attributes.Append(xmlChatMessage.CreateAttribute("text"));
            xmlText.Value = sText;

            this.ioHandler.SlowSend(xmlChatMessage);
        }

        #region Events
        private void OnClientConnect(IAsyncResult asyn)
        {
            try
            {
                SocketInfo socketInfo = (SocketInfo)asyn.AsyncState;
                Socket socServer = socketInfo.ConnectionSocket.EndAccept(asyn);
                socketInfo.ConnectionSocket = socServer;

                if (socketInfo.Type == SocketInfo.SocketTypes.QuickRecieve)
                {
                    this.WaitForQuickData(socketInfo);
                }
                else
                {
                    this.WaitForSlowData(socketInfo);
                }
            }
            catch (SocketException socExp)
            {
                throw socExp;
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        private void OnQuickDataRecieved(IAsyncResult asyn)
        {
            try
            {
                SocketInfo socketInfo = (SocketInfo)asyn.AsyncState;

                //End recieve
                int iRx = 0;
                iRx = socketInfo.ConnectionSocket.EndReceive(asyn);

                //TODO Needs to put in thread
                #region Needs to split off into separate thread
                char[] chars = new char[iRx + 1];
                System.Text.Decoder decoder = System.Text.Encoding.UTF8.GetDecoder();
                int charLen = decoder.GetChars(bytDataBuffer, 0, iRx, chars, 0);
                String sData = new String(chars);

                if (sData != "\0")
                {
                    //Create xml document from string recieved
                    XmlDocument xmlDoc = new XmlDocument();
                    xmlDoc.LoadXml(sData);

                    //Process xml document
                    this.QuickProcessRecievedXML(xmlDoc);

                    //Start waiting for data again
                    this.WaitForQuickData(socketInfo);
                }
                #endregion
            }
            catch (SocketException socEx)
            {
                if (socEx.ErrorCode == 10057)
                {
                    //Ignore error because socket isn't connected
                }
                //Socket was forcibly closed (server disconnected from client)
                else if (socEx.ErrorCode == 10054)
                {
                    //Log off
                    this.Logoff();
                }
                else
                {
                    throw socEx;
                }
            }
            catch (XmlException xmlEx)
            {
                throw xmlEx;
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        private void OnSlowDataRecieved(IAsyncResult asyn)
        {
            try
            {
                SocketInfo socketInfo = (SocketInfo)asyn.AsyncState;

                //End recieve
                int iRx = 0;
                iRx = socketInfo.ConnectionSocket.EndReceive(asyn);

                char[] chars = new char[iRx + 1];
                System.Text.Decoder decoder = System.Text.Encoding.UTF8.GetDecoder();
                int charLen = decoder.GetChars(bytDataBuffer, 0, iRx, chars, 0);
                String sData = new String(chars);

                if (sData != "\0")
                {
                    //Create xml document from string recieved
                    XmlDocument xmlDoc = new XmlDocument();
                    xmlDoc.LoadXml(sData);

                    //TODO make in new thread
                    //Process xml document
                    this.SlowProcessRecievedXML(xmlDoc);

                    //Start waiting for data again
                    this.WaitForSlowData(socketInfo);
                }
            }
            catch (SocketException socEx)
            {
                if (socEx.ErrorCode == 10057)
                {
                    //Ignore error because socket isn't connected
                }
                //Socket was forcibly closed (server disconnected from client)
                else if (socEx.ErrorCode == 10054)
                {
                    //Log off
                    this.Logoff();
                }
                else
                {
                    throw socEx;
                }
            }
            catch (XmlException xmlEx)
            {
                //Do nothing
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        #endregion
    }
}
